1   /*                        __    __  __  __    __  ___
2    *                       \  \  /  /    \  \  /  /  __/
3    *                        \  \/  /  /\  \  \/  /  /
4    *                         \____/__/  \__\____/__/.ɪᴏ
5    * ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
6    */
7   package io.vavr.collection.euler;
8   
9   import io.vavr.Function1;
10  import io.vavr.collection.CharSeq;
11  import io.vavr.collection.List;
12  import io.vavr.collection.Stream;
13  import org.assertj.core.api.Assertions;
14  import org.junit.Test;
15  
16  import static io.vavr.collection.euler.Utils.file;
17  import static io.vavr.collection.euler.Utils.readLines;
18  import static org.assertj.core.api.Assertions.assertThat;
19  
20  public class Euler42Test {
21  
22      /**
23       * <strong>Problem 42 Coded triangle numbers</strong>
24       * <p>
25       * The <i>n</i><sup>th</sup> term of the sequence of triangle numbers is
26       * given by, <i>t</i><sub>n</sub> = ½<i>n</i>(<i>n</i>+1); so the first ten
27       * triangle numbers are:
28       * <pre>
29       * 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...
30       * </pre>
31       * <p>
32       * By converting each letter in a word to a number corresponding to its
33       * alphabetical position and adding these values we form a word value. For
34       * example, the word value for SKY is 19 + 11 + 25 = 55 = t<sub>10</sub>. If
35       * the word value is a triangle number then we shall call the word a
36       * triangle word.
37       * <p>
38       * Using p042_words.txt, a 16K text file containing nearly two-thousand
39       * common English words, how many are triangle words?
40       * <p>
41       * See also <a href="https://projecteuler.net/problem=42">projecteuler.net
42       * problem 42</a>.
43       */
44      @Test
45      public void shouldSolveProblem42() {
46          assertThat(isTriangleWord("SKY")).isTrue();
47          assertThat(sumOfAlphabeticalPositions("SKY")).isEqualTo(55);
48          assertThat(alphabeticalPosition('S')).isEqualTo(19);
49          assertThat(alphabeticalPosition('K')).isEqualTo(11);
50          assertThat(alphabeticalPosition('Y')).isEqualTo(25);
51          assertThat(TRIANGLE_NUMBERS.take(10)).containsExactly(1, 3, 6, 10, 15, 21, 28, 36, 45, 55);
52          List.rangeClosed(1, 60).forEach(n -> Assertions.assertThat(isTriangleNumberMemoized.apply(n)).isEqualTo(List.of(1, 3, 6, 10, 15, 21, 28, 36, 45, 55).contains(n)));
53  
54          assertThat(numberOfTriangleNumbersInFile()).isEqualTo(162);
55      }
56  
57      private static int numberOfTriangleNumbersInFile() {
58          return readLines(file("p042_words.txt"))
59                  .map(l -> l.replaceAll("\"", ""))
60                  .flatMap(l -> List.of(l.split(",")))
61                  .filter(Euler42Test::isTriangleWord)
62                  .length();
63      }
64  
65      private static boolean isTriangleWord(String word) {
66          return isTriangleNumber(sumOfAlphabeticalPositions(word));
67      }
68  
69      private static boolean isTriangleNumber(int n) {
70          return TRIANGLE_NUMBERS
71                  .takeWhile(t -> t <= n)
72                  .exists(t -> t == n);
73      }
74  
75      private static final Function1<Integer, Boolean> isTriangleNumberMemoized = Function1.of(Euler42Test::isTriangleNumber).memoized();
76  
77      private static final Stream<Integer> TRIANGLE_NUMBERS = Stream.from(1).map(n -> 0.5 * n * (n + 1)).map(Double::intValue);
78  
79      private static int sumOfAlphabeticalPositions(String word) {
80          return CharSeq.of(word)
81                  .map(Euler42Test::alphabeticalPosition)
82                  .sum().intValue();
83      }
84  
85      private static int alphabeticalPosition(char c) {
86          return c - 'A' + 1;
87      }
88  }